home *** CD-ROM | disk | FTP | other *** search
-
- /* 3D graphics renderer core: all algoritims, math and assembly */
- /* by Dave Stampe */
-
- /* Completely rewritten by Dave Stampe, December 1993 */
-
- /* Contact: dstampe@sunee.waterloo.edu */
-
- /* Copyright 1992 by Dave Stampe and Bernie Roehl.
- May be freely used to write software for release into the public domain;
- all commercial endeavours MUST contact Bernie Roehl and Dave Stampe
- for permission to incorporate any part of this software into their
- products!
-
- ATTRIBUTION: If you use any part of this source code or the libraries
- in your projects, you must give attribution to REND386, Dave Stampe,
- and Bernie Roehl in your documentation, source code, and at startup
- of your program. Let's keep the freeware ball rolling!
- */
-
- #include <stdio.h>
- #include <ctype.h>
- #include <assert.h>
- #include <alloc.h>
- #include <dos.h>
- #include <mem.h>
-
- #include "3dstruct.h" // renderer structures
- #include "intmath.h" // integer math
- #include "rendpriv.h" // assembler routines
- #include "renderer.h"
- #include "viewstat.h" // DGROUP static viewport work
-
- #include "xmem.h"
-
-
- /************* VIEWPORT CONTROL ************/
-
- /* compute eye point/ angle movement factors only */
-
- void real_viewpoint(VIEW *v, long *x, long *y, long *z)
- {
- *x = v->eye_xform[3][0] ;
- *y = v->eye_xform[3][1] ;
- *z = v->eye_xform[3][2] ;
- }
-
-
- void matrix_view_factors(VIEW *v, MATRIX m) /* set up from matrix xform */
- {
- matrix_transpose(m, v->eye_xform); /* copy matrix rotational inverse */
- v->eye_xform[3][0] = m[3][0]; /* translation is viewpoint */
- v->eye_xform[3][1] = m[3][1];
- v->eye_xform[3][2] = m[3][2];
- }
-
-
- void view_to_matrix(VIEW *v,MATRIX m) /* view matrix to xform matrix */
- {
- matrix_transpose(v->eye_xform, m); /* copy matrix rotational inverse */
- m[3][0] = v->eye_xform[3][0]; /* direct inverse gaze point */
- m[3][1] = v->eye_xform[3][1];
- m[3][2] = v->eye_xform[3][2];
- }
-
-
-
- /************ VERTEX AND POLY COPY MEMORY ALLOCATION ************/
-
- #define MAXVERTICES 20
-
- static NVERTEX *nvert[MAXVERTICES+10]; /* table of new poly vertices created */
- static int nvcount; /* table pointer/count (Z clip pass) */
- static int vtxcount; /* number of vertices produced by clipper */
-
-
- static DSORT *vispolys = NULL; /* an array of pointers to visible polygons */
- static DSORT *visobjs = NULL; /* used for object-first sort */
-
- static DSORT *polist; /* which array to put polys in */
-
-
- void *vtxram = NULL; /* memory allocation area start */
-
- static unsigned render_mem = 0; /* size of memory */
- static int totpolys = 0;
- static int maxpolys = 1200;
-
- static OK; /* cleared if too many vertices */
-
- NVERTEX *nvalloc; /* memory alloc ptrs */
- NPOLY *npalloc; /* also used by external ASM routines */
-
-
- void reset_render() /* free copy space */
- {
- if (vtxram) free(vtxram);
- if (vispolys) free(vispolys);
- if (visobjs) free(visobjs);
- vtxram = visobjs = vispolys = NULL;
- }
-
- /* get space for poly and vertex working copies */
- void *setup_render(unsigned mem, int polys)
- {
- atexit(reset_render);
-
- maxpolys = polys;
- render_mem = mem<<10; /* number of K bytes */
-
- if (mem<16)
- {
- fprintf(stderr,"\nMust allocate at least 16K of memory for renderer!\n");
- return NULL;
- }
-
- if (mem>63)
- {
- fprintf(stderr,"\nCannot allocate more than 64K of memory for renderer!\n");
- return NULL;
- }
-
- if( (NULL==(vtxram = (NVERTEX *)calloc(mem,1024))) ||
- (NULL==(vispolys = (DSORT *)calloc(maxpolys,sizeof(DSORT)))) ||
- (NULL==(visobjs = (DSORT *)calloc(maxpolys,sizeof(DSORT)))) )
- {
- fprintf(stderr,"\nCannot allocate memory for renderer!\n");
- return NULL;
- }
-
- npalloc = (NPOLY *)vtxram;
- nvalloc = (NVERTEX *)((char *)vtxram+render_mem-50);
- if(init_math()) return NULL;
- return vtxram;
- }
-
-
- static void init_render() /* reclaim all vertex and poly space */
- {
- npalloc = (NPOLY *)vtxram;
- nvalloc = (NVERTEX *)((char *)vtxram+render_mem-50);
- }
-
-
-
- static NVERTEX *newvertex() /* alloc space for new vertex copy */
- { /* nvalloc always = allocated vertex */
- --nvalloc;
- nvalloc->perspect = 0; // initialize
- if(FP_OFF(nvalloc)-FP_OFF(npalloc) < 200U) OK = 0; // memory OK?
- return nvalloc;
- }
-
-
- static NPOLY *newpoly() /* alloc space for new poly copy */
- { /* vertex array follows but allocated later */
- NPOLY *p = npalloc++;
- if(FP_OFF(nvalloc)-FP_OFF(npalloc) <
- (200U + MAXVERTICES*sizeof(NVERTEX *)) ) OK = 0; // memory OK?
- return p;
- }
-
-
-
- /********* Z CLIP AND VERTEX COPY *********/
-
-
- static NVERTEX *clip_z_int(VERTEX *v1, VERTEX *v2)
- {
- NVERTEX *nv1,*nv2,*nv3;
-
- if ((nv1=v1->new_copy)==NULL)
- nv1 = xy_transform(v1); // make sure that the
- if ((nv2=v2->new_copy)==NULL)
- nv2 = xy_transform(v2); // vertices are ready
-
- return z_hither_clip(nv1, nv2);
- }
-
-
- /*************** POLYGON CLIP AND PROCESS *************/
-
- static unsigned hilite_flag = 0;
-
- /*********** Z CLIP POLYGON, PROCESS XY IF OK *********/
-
- static int zclip_loop(POLY *p) // do in C as assembler can't help much (BC 3.1)
- {
- int xy_outcode_or = 0;
- int xy_outcode_and = 15;
- int j;
-
- char first_z_out; /* first vertex Z outcode */
- VERTEX *first_z_vtx; /* orig. (world) first vertex */
- char last_z_out; /* previous vertex Z outcode */
- VERTEX *last_z_vtx; /* orig. (world) prev. vertex */
- char z_ocode;
- VERTEX **pv = p->points;
-
- first_z_vtx = last_z_vtx = *pv;
- if ((first_z_out = last_z_out = z_ocode = first_z_vtx->z_outcode & HITHER)==0)
- {
- register int i = z_output( nvert[nvcount] = xy_transform(first_z_vtx) );
- xy_outcode_or |= i;
- xy_outcode_and &= i;
- if(nvcount++ > MAXVERTICES) OK = 0;
- }
-
- for (j=p->npoints;j>1;j--)
- {
- VERTEX *
- v = *(++pv);
- z_ocode = v->z_outcode & HITHER;
- if (z_ocode != last_z_out)
- { // create clipped vertex
- register int i = z_output( nvert[nvcount] = clip_z_int(last_z_vtx, v) );
- xy_outcode_or |= i;
- xy_outcode_and &= i;
- if(nvcount++ > MAXVERTICES) OK = 0;
- }
- last_z_vtx = v;
- if ((last_z_out = z_ocode)==0) // convert original if OK
- {
- register int i = z_output( nvert[nvcount] = xy_transform(v) );
- xy_outcode_or |= i;
- xy_outcode_and &= i;
- if(nvcount++ > MAXVERTICES) OK = 0;
- }
- }
-
- if (first_z_out != last_z_out) // do we need to flush clipper?
- {
- register int i = z_output( nvert[nvcount] =
- clip_z_int(last_z_vtx, first_z_vtx) );
- xy_outcode_or |= i;
- xy_outcode_and &= i; // create clipped vertex
- if(nvcount++ > MAXVERTICES) OK = 0;
- }
-
- if(xy_outcode_and) return -1; // -1 if poly will be completely destroyed
- return (xy_outcode_or); // 0 if no XY clip required
- }
-
-
- /************ POLYGON PROCESSING PIPELINE *********/
-
- static int depth_type; /* selects depth sort style */
-
-
- static void proc_poly(POLY *p) /* accept/reject tests on polys */
- { /* transforms vertices, clips */
- int i,j,k; /* and computes screen coords */
- char z_outcode_or = 0; /* Also copies polys and points */
- char z_outcode_and = 3; /* for minimum disruption of */
- /* the world database */
-
- NPOLY *np; // new poly copy
- NVERTEX **nvp = &nvert[0]; // pointer into vertex list
- NVERTEX **vpoly; // vertex pointer storage allocation
-
-
- long poly_depth;
- int xy_outcode_or; // 0 if poly needs no clipping
-
- if(p->npoints>2 && is_poly_facing(p)>= 0) return; // backfacing poly?
- else
- {
- int i;
- VERTEX **v = p->points;
-
- for(i=p->npoints;i>0;i--) // z transform all vertices
- {
- int o = z_convert_vertex(*v++);
- z_outcode_or |= o;
- z_outcode_and &= o;
- }
- }
-
- if (z_outcode_and == HITHER ||
- z_outcode_and == YON) return; /* all hither/yon? Reject poly */
-
- /* otherwise, begin Z clip and XY transforms */
-
-
- /* Pass 2: */
- /* Z-clip and XY conv. vertices */
- /* also make copies to temp array */
-
- nvcount = 0; // initialize vertex buffer
-
- xy_outcode_or = zclip_loop(p);
-
- if( (nvcount<3 && p->npoints>2) || // poly and degenerate
- xy_outcode_or == -1) // completely outside window
- {
- return; /* reject poly if degenerate */
- }
-
- // ////////////// OK, we have a set of XY vertices. ///////////
-
- np = newpoly(); // create polygon copy, initialize
- np->parent = p;
-
- vpoly = (NVERTEX **)npalloc; /* used to allocate space for new poly vertex */
- /* pointer list: vertex ptrs after poly struct */
-
- if(depth_type & AVERAGE) // depth before clipping XY
- {
- poly_depth = average_nvertex_depth(nvp, nvcount);
- }
- else /* default: use deepest Z in polygon */
- {
- poly_depth = deepest_nvertex_depth(nvp, nvcount);
- }
-
- if(depth_type & ATBACK) poly_depth |= 0x40000000; // force to back
-
- np->maxz = poly_depth;
-
- if((xy_outcode_or) == 0) /* does poly need XY clipping? */
- {
- memcpy(vpoly, nvp, nvcount*sizeof(NVERTEX *) ); // no, copy to list
- np->npoints = vtxcount = nvcount;
- }
- else /* yes: XY clip it to list */
- {
- vtxcount = XY_clip_array(nvp,vpoly,nvcount);
- if(vtxcount==-1)
- {
- OK = 0; // copy: out of vertex space!
- return;
- }
-
- if((vtxcount<3 && p->npoints>2) || vtxcount<2)
- {
- return; /* discard degenerate poly */
- }
- np->npoints = vtxcount;
- }
-
- vpoly += vtxcount; // alloc space for vertex ptrs
- npalloc = (NPOLY *)vpoly; /* update space pointer */
-
- np->color = user_poly_color(p, p->color|hilite_flag, p->npoints, poly_depth); /* user poly color select */
-
- /* add to list of polys to render */
- if (totpolys < maxpolys)
- {
- polist[totpolys].ptr = np;
- polist[totpolys++].depth = poly_depth & 0xFFFFFFFE;
- }
- else OK = 0;
- }
-
-
-
-
-
- /************ ALLOWS EXTERNAL (HORIZON) POLYS TO USE CLIPPER ***********/
-
- void submit_poly(NPOLY *p, long maxz); // fwd decl
-
- void render_ext_poly(int npoints, int vx[20], int vy[20], unsigned color)
- {
- NPOLY *ext_poly = NULL;
- int ext_oor, ext_oand; /* outcode and, or */
- NVERTEX **ext_vlist; /* used to store original vertexes */
- NVERTEX **vpoly; // vertex storage allocation
- int i;
-
- if(npoints<3) return;
-
- init_render();
- ext_poly = newpoly();
- if(!ext_poly) return;
- ext_vlist = &(nvert[0]);
- vpoly = (NVERTEX **) npalloc; // save memory ptr
-
- for(i=0; i<npoints;i++)
- {
- int ocode = 0;
-
- NVERTEX *v = newvertex();
- long x = vx[i];
- long y = vy[i];
-
- v->xs = x<<2;
- v->ys = y<<2;
- if((x<<2)<VS_left4) ocode |= LEFT;
- if((x<<2)>VS_right4) ocode |= RIGHT;
- if((y<<2)<VS_top4) ocode |= TOP;
- if((y<<2)>VS_bottom4) ocode |= BOTTOM;
- ext_oor |= ocode;
- ext_oand &= ocode;
- v->outcode = ocode;
- *ext_vlist++ = v;
- }
-
- ext_poly->color = color;
- ext_poly->parent = NULL;
-
- if(ext_oand) return; /* reject poly if degenerate */
- ext_poly->npoints = npoints;
- ext_vlist = &(nvert[0]);
-
- if((ext_oor) == 0) /* does poly need XY clipping? */
- {
- for(i=0;i<npoints;i++) *vpoly++ = *ext_vlist++;
- }
- else /* yes: XY clip it */
- {
- vtxcount = XY_clip_array(ext_vlist,vpoly,npoints);
- if(vtxcount==-1)
- {
- OK = 0;
- return;
- }
- vpoly += vtxcount;
-
- if(vtxcount==0) return;
- ext_poly->npoints = vtxcount;
- }
-
- npalloc = (NPOLY *)vpoly; /* update space pointer */
-
- submit_poly(ext_poly, 0x7FFFFFFFL);
-
- init_render();
-
- }
-
-
- /********** SCREEN-POINT MONITOR ***********/
-
-
- static OBJECT *current_object; // needed for memory mapping of monitor
-
- static int monitor_test_enabled = 0;
-
- static int monitor_test_point_x; // screen coords
- static int monitor_test_point_y;
- static int monitor_test_point_tx; // prescaled renderer coords
- static int monitor_test_point_ty;
-
- static unsigned monitor_min_vtx_distance; // prescaled x4, city block dist
- static int monitor_which_vertex; /* npoly vertex # closest <UNTESTED YET> */
- // set to -1 when new best poly found
- // to force after-subrender rescan
- static POLY *monitor_which_poly;
-
- static OBJECT *monitor_which_object;
-
- static long monitor_poly_depth;
- static long monitor_min_depth = 0;
-
-
- static void monitor_vertex_scan() // scans for vertex closest to point
- { // do after end of subrender (poor speed)
- int i,j,n;
- VERTEX **vt;
- VERTEX *v;
- NVERTEX *nv;
- unsigned dm, closest = monitor_min_vtx_distance;
-
- if(monitor_which_poly == NULL) return; // none found
- if(monitor_which_vertex != -1) return; // no need to scan
-
- accessptr(monitor_which_poly); // access memory!
-
- vt = monitor_which_poly->points; // we scan original poly for order
- i = monitor_which_poly->npoints;
- n = -1;
-
- for(j=0;j<i;j++)
- {
- v = *vt++;
- if(v->z_outcode!=0 || v->new_copy==NULL) continue; // have a renderer vtx?
- nv = v->new_copy;
-
- if(nv->outcode) continue; // skip if clipped
-
- dm = abs(nv->xs - monitor_test_point_tx) + // is it closest?
- abs(nv->ys - monitor_test_point_ty);
- if (dm<closest)
- {
- closest = dm;
- n = j;
- }
- }
- monitor_which_vertex = n;
- }
-
- void set_screen_monitor(int x, int y)
- {
- monitor_test_enabled = 1;
- monitor_test_point_x = x;
- monitor_test_point_y = y;
- monitor_test_point_tx = x<<2; // prescaled
- monitor_test_point_ty = y<<2;
- monitor_poly_depth = 0x7FFFFFFF;
- monitor_which_poly = NULL;
- monitor_which_object = NULL;
- monitor_which_vertex = -1;
- monitor_min_depth = 0;
- monitor_min_vtx_distance = 200; // prescaled x4, city block dist
- }
-
-
- void clear_screen_monitor()
- {
- monitor_test_enabled = 0;
- }
-
-
- OBJECT *screen_monitor_object()
- {
- return monitor_which_object;
- }
-
- POLY *screen_monitor_poly()
- {
- return monitor_which_poly;
- }
-
- int screen_monitor_vertex()
- {
- return monitor_which_vertex;
- }
-
-
- /************ UNPACK NPOLY VERTICES, RENDER **********/
-
-
- static int pcoords[MAXVERTICES*2]; /* WHERE VERTICES GO TO BE DRAWN */
-
- // routine used to draw polys
- extern void user_render_poly(WORD vertex_count, WORD *pcoords,
- SURFACE poly_color, COORD max_depth);
-
-
- /* copies poly data, submits to renderer */
- /* copy in reverse order if flipped screen */
- /* if monitor turned on, checks poly too */
- static void submit_poly(NPOLY *p, long maxz)
- {
- int number;
- int polarity;
- int vtx;
-
- polarity = VS_orientation & (XFLIP|YFLIP);
- if(polarity==(XFLIP|YFLIP)) polarity = 0;
-
- number = unpack_poly_vertices(p, pcoords, polarity);
-
- if(monitor_test_enabled)
- {
- if ( number>2 && p->parent &&
- maxz<monitor_poly_depth &&
- maxz>monitor_min_depth )
-
- {
- if(-1 != monitor_test_poly(monitor_test_point_x,
- monitor_test_point_y,
- number, &pcoords[0]) )
- {
- monitor_poly_depth = maxz; // new search goal
- monitor_which_vertex = -1; // mark rescan needed
- monitor_which_poly = p->parent; // record poly
- accessptr(monitor_which_poly); // make memory accesible
- monitor_which_object = monitor_which_poly->object; // hold onto object!
- }
- }
- }
-
- user_render_poly(number, &pcoords[0], p->color, maxz);
- }
-
-
- /*********** LIGHTING SUPPORT ***********/
-
- // given poly, computes its cosine*127 from its first
- // vertex to the given point. Used for lighting.
-
- extern int light_cosine(long nx, long ny, long nz, // poly normal
- long vx, long vy, long vz, // poly vertex
- long x, long y, long z); // light location
-
- int compute_poly_cosine(POLY *p, long x, long y, long z, int vect)
- {
- accessptr(p); // map in EMM
-
- if(vect==0)
- return light_cosine(p->normalx, p->normaly, p->normalz,
- p->points[0]->x,p->points[0]->y,p->points[0]->z,
- x, y, z);
- else
- return light_cosine(p->normalx, p->normaly, p->normalz, 0,0,0, x, y, z);
- }
-
-
-
- /*********** OBJECT-RENDERING CONTROL **********/
-
- // called before drawing obj
- static void (*update_obj_handler)(OBJECT *o) = NULL;
-
- // sets up routine to be called when an object's
- // representation is to be displayed that needs updating
- // usually used to move representations to match object
- void *set_renderer_update_handler(void (*handler)(OBJECT *o) )
- {
- void *h = update_obj_handler;
-
- update_obj_handler = handler;
-
- return h;
- }
-
-
- static int proc_obj(OBJECT *obj, long centz)
- {
- REP *repp;
- long oscreen;
-
- static int i; // SCOPING BUG
- static POLY *p;
-
- if(obj==NULL) return 1;
-
- if(obj->oflags&OBJ_REPLOCK) // never change representation
- {
- repp = obj->current_rep;
- goto use_it;
- }
-
- if((repp = obj->replist)==NULL) return 1; /* no representation */
- if(repp->size==0) goto usethis; /* 0 size always drawn */
- if(centz<VS_hither) goto usethis; /* bad center */
-
- oscreen = compute_obj_screen_size(obj, centz); // width on screen
- if(oscreen<0) goto usethis;
-
- while(repp!=NULL) /* choose representation to use: */
- { /* 0 or just smaller size always drawn */
- if(oscreen>=(repp->size)) goto usethis;
- if(repp->next!=NULL) repp = repp->next;
- else return 1; // too small: don't draw at all
- }
-
- usethis: // set new representation
- obj->current_rep = repp;
- if(repp==NULL) // exit if still nothing
- return 1;
-
- use_it:
-
- // THIS CODE DEPENDS ON WHAT UPDATING THE REPRESENTATION
- // MAY NEED. HERE, IT MAY NEED TO BE MOVED TO MATCH THE
- // CACHED OBJECT POSITION.
- // THE ROUTINE SHOULD INCREMENT THE UPDATE COUNTS EACH
- // TIME IT IS CALLED, OR LEAVE THEM DIFFERENT IF IT
- // IS TO BE CALLED EVERY TIME
-
- if(obj->update_count != repp->update_count && update_obj_handler!= NULL)
- {
- accessptr(repp->polys); // used for mapping EMM
-
- update_obj_handler(obj);
- }
-
- hilite_flag = (obj->oflags&OBJ_HIGHLIGHTED) ? 0x8000 : 0; // auto-hilite object
- accessptr(repp->polys); // used for mapping EMM
-
- prerender_clear_object(obj);
-
- for(i=repp->npolys,p=repp->polys;i>0;i--)
- {
- proc_poly(p++);
- if(!OK) break;
- }
-
- return 0; /* return 0, object was drawn */
- }
-
-
- /*************** OBJECT LIST RENDERING *************/
-
-
- #define OUT_OF_VIEW 0x80000000L // returned if we can't see object
-
- // EXTERNAL renderer interface
-
- extern void user_setup_blitter();
- extern void user_reset_blitter();
-
-
- void subrender(OBJLIST *objlist)
- {
- static OBJECT *obj;
- static int i; // SCOPING BUG!
- static int snpoly = 0;
- static int nobs = 0;
- static long center_z;
- static unsigned f;
-
- init_render();
- user_setup_blitter(); /* draw the polys */
-
- if(objlist==NULL ||
- objlist->nnext==NULL ||
- objlist->nnext==objlist->prev) return;
-
- totpolys = 0; // BY_OBJ SORTING DISABLED FOR NOW
- OK = 1;
- /* step 1: sort objects and polys together */
- polist = visobjs;
- for (obj = objlist->nnext; obj; obj = obj->nnext)
- {
- // if(!obj) break;
- f = obj->oflags;
- if (!(f&IS_VISOBJ)) continue; // non-visible object type
- if (f & OBJ_INVIS) continue; /* invisible object */
-
- if ((center_z=obj_clip_by_volume(obj)) != OUT_OF_VIEW)
- {
- if((depth_type = obj->oflags)&BYOBJECT)
- {
- // obj->oflags |= IS_VISOBJ;
- polist[totpolys].ptr = (NPOLY *) obj; /* obj. depth only */
- polist[totpolys++].depth = center_z | 1;
- }
- else
- {
- proc_obj(obj, center_z); /* all polys in object */
- if(OK==0) break;
- }
- }
- }
-
- if(totpolys>16)
- qsort_dsort( &visobjs[0], &visobjs[totpolys-1] );
- else if(totpolys>1)
- insertion_dsort( &visobjs[0], &visobjs[totpolys-1] );
-
- nobs = totpolys;
-
- totpolys = 0;
- polist = vispolys;
-
- for (i=0;i<nobs;i++) /* now expand objects */
- {
- if((visobjs[i].depth & 1)==0)
- {
- memcpy(&vispolys[totpolys++], &visobjs[i], sizeof(DSORT)); /* just copy polys */
- }
- else
- {
- snpoly = totpolys; /* expand objects */
- proc_obj((OBJECT *) visobjs[i].ptr, visobjs[i].depth);
- if(OK==0) break;
- if(totpolys-snpoly>16)
- qsort_dsort( &vispolys[snpoly], &vispolys[totpolys-1] );
- if(totpolys-snpoly>1)
- insertion_dsort( &vispolys[snpoly], &vispolys[totpolys-1] );
- }
- if(totpolys >= maxpolys) break;
- }
-
- for (i = 0; i < totpolys; i++)
- submit_poly(vispolys[i].ptr,vispolys[i].depth);
- user_reset_blitter();
-
- if (monitor_test_enabled && monitor_which_poly!=NULL);
- {
- accessptr(monitor_which_poly); // make memory accesible
- monitor_vertex_scan(); // if a poly found, process it
- }
- }
-
-
- void render(OBJLIST *objlist, VIEW *view)
- {
- render_set_view(view);
- subrender(objlist);
- }
-